<!-- 拖拽等操作的容器 -->
<template>
  <div
    ref="wrapper"
    class="draggable-resizable"
    :style="dragStyle"
    :class="[{
      [classNameActive]: enabled,
      [classNameDragging]: dragging,
      [classNameResizing]: resizing,
      [classNameDraggable]: draggable,
      [classNameResizable]: resizable
    }]"
    @mousedown="elementDown"
    @touchstart="elementTouchDown"
  >
    <!-- 旋转 -->
    <div
      v-if="resizable"
      :class="[classNameHandle, classNameHandle + '-rotate']"
      :style="{display: enabled ? 'block' : 'none'}"
      @mousedown.stop.prevent="handleRotateDown"
    >
      <i class="el-icon-refresh"></i>
    </div>
    <!-- 变形 + 4角样式 -->
    <div
      v-for="item of actualHandles"
      :key="item"
      :class="[classNameHandle, classNameHandle + '-' + item]"
      :style="{display: enabled ? 'block' : 'none'}"
      @mousedown.stop.prevent="handleDown(item, $event)"
      @touchstart.stop.prevent="handleTouchDown(item, $event)"
    >
      <slot :name="item"></slot>
    </div>
    <slot />
  </div>
</template>

<script>
import {
  addEvent,
  removeEvent,
  matchesSelectorToParentElements
} from './dom'
// 不可选
const userSelectNone = {
  userSelect: 'none',
  MozUserSelect: 'none',
  WebkitUserSelect: 'none',
  MsUserSelect: 'none'
}
// 用户可选
const userSelectAuto = {
  userSelect: 'auto',
  MozUserSelect: 'auto',
  WebkitUserSelect: 'auto',
  MsUserSelect: 'auto'
}
// 鼠标事件, 兼容mouse/touch
const events = {
  mouse: {
    start: 'mousedown',
    move: 'mousemove',
    stop: 'mouseup'
  },
  touch: {
    start: 'touchstart',
    move: 'touchmove',
    stop: 'touchend'
  }
}
let eventsFor = events.mouse
export default {
  name: 'DesignDraggableResizable',
  props: {
    handles: { // 变形外边角名
      type: Array,
      default: () => ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'], // topLeft, topMiddle, topRight, middleRight, bottomRight, bottomMiddle, bottomLeft, middleLeft
      validator: val => {
        const s = new Set(['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'])
        return new Set(val.filter(h => s.has(h))).size === val.length // 验证传入的边角名是否正确
      }
    },
    deselectElement: { // 取消选择的class选择器
      type: String,
      default: null
    },
    dragCancel: { // 组件节点内的取消节点选择器
      type: String,
      default: null
    },
    dragHandle: { // 组件节点内的有效节点选择器，不传则会取整个组件节点
      type: String,
      default: null
    },
    // 状态
    disableUserSelect: { // 禁止用户选择
      type: Boolean,
      default: true
    },
    parent: { // 是否是父元素?
      type: Boolean,
      default: false
    },
    preventDeactivation: { // 防止停用
      type: Boolean,
      default: false
    },
    resizable: { // 是否可调整尺寸
      type: Boolean,
      default: true
    },
    active: { // 是否激活
      type: Boolean,
      default: false
    },
    draggable: { // 是否可拖动
      type: Boolean,
      default: true
    },
    enableNativeDrag: { // 启用原生拖动, 会使onDragStart返回false以阻止拖动
      type: Boolean,
      default: false
    },
    // 样式
    classNameHandle: { // 元素边角的拖动样式
      type: String,
      default: 'handle'
    },
    w: { // 宽
      type: Number,
      default: 200,
      validator: val => val > 0
    },
    h: { // 高
      type: Number,
      default: 200,
      validator: val => val > 0
    },
    x: { // 左偏移
      type: Number,
      default: 0,
      validator: val => typeof val === 'number'
    },
    y: { // 上偏移
      type: Number,
      default: 0,
      validator: val => typeof val === 'number'
    },
    r: { // 旋转
      type: Number,
      default: 0,
      validator: val => typeof val === 'number'
    },
    z: { // 层级
      type: [Number, String],
      default: 'auto',
      validator: val => (typeof val === 'string' ? val === 'auto' : val >= 0)
    },
    minWidth: { // 最小宽
      type: Number,
      default: 0,
      validator: val => val >= 0
    },
    minHeight: { // 最小高
      type: Number,
      default: 0,
      validator: val => val >= 0
    },
    maxWidth: { // 最大宽
      type: Number,
      default: 0,
      validator: val => val >= 0
    },
    maxHeight: { // 最大高
      type: Number,
      default: 0,
      validator: val => val >= 0
    },
    scale: { // 缩放
      type: Number,
      default: 1,
      validator: val => val > 0
    },
    lockAspectRatio: { // 锁定纵横比
      type: Boolean,
      default: false
    },
    grid: { // 格子
      type: Array,
      default: () => [1, 1]
    },
    axis: { // 辅助线, 横向、纵向、都有
      type: String,
      default: 'both',
      validator: val => ['x', 'y', 'both'].includes(val)
    },
    snap: { // 元素对齐
      type: Boolean,
      default: false
    },
    snapTolerance: { // 当调用对齐时，用来设置组件与组件之间的对齐距离，以像素为单位
      type: Number,
      default: 5,
      validator: val => typeof val === 'number'
    },
    // 事件
    onDragStart: { // 鼠标事件按下的前置操作
      type: Function,
      default: null
    },
    onResizeStart: { // 旋转的前置检查操作
      type: Function,
      default: null
    }
  },
  data () {
    return {
      // class样式, 可通过
      classNameActive: 'active',
      classNameDragging: 'dragging',
      classNameResizing: 'resizing',
      classNameDraggable: 'draggable',
      classNameResizable: 'resizable',
      // 状态层
      dragging: true, // 是否正在拖动
      enabled: this.active, // 是否被激活
      resizing: false, // 是否正在改变尺寸
      // 元素层
      parentWidth: null, // 父层元素宽度
      parentHeight: null, // 父层高度
      left: this.x, // 左偏移
      top: this.y, // 上偏移
      right: null, // 右偏移
      bottom: null, // 下偏移
      rawWidth: this.w, // 宽度
      rawHeight: this.h, // 高度
      rawLeft: this.x, // 距左?
      rawTop: this.y, // 距上?
      rawRight: null, // 距右?
      rawBottom: null, // 距下?
      rawRotate: this.r, // 旋转
      rotate: this.r, // 起始角度
      rotateOpt: null, // 旋转的数据
      zIndex: this.z, // 层级
      minW: this.minWidth, // 最小宽
      minH: this.minHeight, // 最小高
      maxW: this.maxWidth, // 最大宽
      maxH: this.maxHeight, // 最大高
      aspectFactor: this.w / this.h, // 锁定宽高比的系数
      // 组件数据
      mouseClickPosition: {}, // 鼠标点击位置, 便于计算
      bounds: {}, // 组件的位置边界, 或者可放大缩小的边界
      handle: null, // 
      currentHandle: null // 当前点击的边角名
    }
  },
  computed: {
    // 可拖动组件最外层容器样式
    dragStyle () {
      return {
        position: 'absolute',
        top: this.top + 'px',
        left: this.left + 'px',
        width: this.width + 'px',
        height: this.height + 'px',
        zIndex: this.zIndex,
        opacity: this.opacity,
        transform: `rotate(${this.rotate}deg)`,
        ...(this.dragging && this.disableUserSelect ? userSelectNone : userSelectAuto)
      }
    },
    // 变形外边角
    actualHandles () {
      if (!this.resizable) return []
      return this.handles
    },
    width () { // 当前元素的宽 = 父层元素的宽度 - 左偏移 - 右偏移
      return this.parentWidth - this.left - this.right
    },
    height () {
      return this.parentHeight - this.top - this.bottom
    },
    // 是否是横向(左右)调整大小
    resizingOnX () {
      return (Boolean(this.handle) && (this.handle.includes('l') || this.handle.includes('r')))
    },
    // 是否是纵向(上下)调整大小
    resizingOnY () {
      return (Boolean(this.handle) && (this.handle.includes('t') || this.handle.include('b')))
    }
  },
  watch: {
    active: { // 监听激活状态
      handler (val) {
        if (val) { // 激活
          this.$emit('activated')
        } else { // 停用
          this.$emit('deactivated')
        }
      }
    },
    // 宽度
    w () {
      if (this.resizing || this.dragging) {
        return
      }
      if (this.parent) {
        this.bounds = this.calcResizeLimits()
      }
      const delta = this.width - this.w
      if (delta % this.grid[0] === 0) {
        this.rawRight = this.right + delta
      }
    },
    // 高度
    h () {
      if (this.parent) {
        this.bounds = this.calcResizeLimits()
      }
      const delta = this.height - this.h
      if (delta % this.grid[1] === 0) {
        this.rawBottom = this.bottom + delta
      }
    },
    // 横向偏移量, 监听拖动时对状态管理的数据修改, 引起组件props的更新, 可计算出差值
    x () {
      if (this.resizing || this.dragging) {
        return
      }
      if (this.parent) {
        this.bounds = this.calcDragLimits()
      }
      const delta = this.x - this.left
      if (delta % this.grid[0] === 0) {
        this.rawLeft = this.x
        this.rawRight = this.right - delta
      }
    },
    y () {
      if (this.resizing || this.dragging) {
        return
      }
      if (this.parent) {
        this.bounds = this.calcDragLimits()
      }
      const delta = this.y - this.top
      if (delta % this.grid[1] === 0) {
        this.rawTop = this.y
        this.rawBottom = this.bottom - delta
      }
    },
    z (val) {
      if (val >= 0 || val === 'auto') {
        this.zIndex = val
      }
    },
    // 锁定纵横比Boolean
    lockAspectRatio (val) {
      if (val) {
        this.aspectFactor = this.width / this.height
      } else {
        this.aspectFactor = undefined
      }
    },
    minWidth (val) {
      if (val > 0 && val <= this.width) {
        this.minW = val
      }
    },
    minHeight (val) {
      if (val > 0 && val <= this.height) {
        this.minH = val
      }
    },
    maxWidth (val) {
      this.maxW = val
    },
    maxHeight (val) {
      this.maxH = val
    },
    // 调整尺寸监听
    rawLeft (newLeft) {
      const bounds = this.bounds
      const aspectFactor = this.aspectFactor
      const lockAspectRatio = this.lockAspectRatio
      const left = this.left
      const top = this.top
      if (bounds.minLeft !== null && newLeft < bounds.minLeft) {
        newLeft = bounds.minLeft
      } else if (bounds.maxLeft !== null && newLeft > bounds.maxLeft) {
        newLeft = bounds.maxLeft
      }
      if (lockAspectRatio && this.resizingOnX) { // 锁定比例且是横向调整
        this.rawTop = top - (left - newLeft) / aspectFactor
      }
      this.left = newLeft
    },
    rawRight (newRight) {
      const bounds = this.bounds
      const aspectFactor = this.aspectFactor
      const lockAspectRatio = this.lockAspectRatio
      const right = this.right
      const bottom = this.bottom

      if (bounds.minRight !== null && newRight < bounds.minRight) {
        newRight = bounds.minRight
      } else if (bounds.maxRight !== null && bounds.maxRight < newRight) {
        newRight = bounds.maxRight
      }
      if (lockAspectRatio && this.resizingOnX) {
        this.rawBottom = bottom - (right - newRight) / aspectFactor
      }
      this.right = newRight
    },
    rawTop (newTop) {
      const bounds = this.bounds
      const aspectFactor = this.aspectFactor
      const lockAspectRatio = this.lockAspectRatio
      const left = this.left
      const top = this.top
      if (bounds.minTop !== null && newTop < bounds.minTop) {
        newTop = bounds.minTop
      } else if (bounds.maxTop !== null && bounds.maxTop < newTop) {
        newTop = bounds.maxTop
      }
      if (lockAspectRatio && this.resizingOnY) {
        this.rawLeft = left - (top - newTop) * aspectFactor
      }
      this.top = newTop
    },
    rawBottom (newBottom) {
      const bounds = this.bounds
      const aspectFactor = this.aspectFactor
      const lockAspectRatio = this.lockAspectRatio
      const right = this.right
      const bottom = this.bottom
      if (bounds.minBottom !== null && newBottom < bounds.minBottom) {
        newBottom = bounds.minBottom
      } else if (bounds.maxBottom !== null && bounds.maxBottom < newBottom) {
        newBottom = bounds.maxBottom
      }
      if (lockAspectRatio && this.resizingOnY) {
        this.rawRight = right - (bottom - newBottom) * aspectFactor
      }
      this.bottom = newBottom
    },
    //旋转
    rawRotate (newRotate) {
      this.rotate = newRotate
    },
    
  },
  created () {
    // 重置鼠标及组件位置信息
    this.resetBoundsAndMouseState()
  },
  mounted () {
    if (!this.enableNativeDrag) {
      this.$el.onDragStart = () => false
    }
    // 获取父节点的宽高
    [this.parentWidth, this.parentHeight] = this.getParentSize()
    this.rawRight = this.parentWidth - this.rawWidth - this.rawLeft // ?
    this.rawBottom = this.parentHeight - this.rawHeight - this.rawTop // ?

    this.settingAttribute() // 设置元素对齐

    // **事件绑定**
    const deselectElementSelector = this.deselectElement ? document.querySelector(this.deselectElement) : document.documentElement
    addEvent(deselectElementSelector, 'mousedown', this.deselect)
    addEvent(deselectElementSelector, 'touchend touchcancel', this.deselect)
    addEvent(window, 'resize', this.checkParentSize)
  },
  beforeDestroy () {
    // **事件解绑**
    const deselectElementSelector = this.deselectElement ? document.querySelector(this.deselectElement) : document.documentElement
    removeEvent(deselectElementSelector, 'mousedown', this.deselect)
    removeEvent(deselectElementSelector, 'touchend touchcancel', this.deselect)
    removeEvent(document.documentElement, 'touchstart', this.handleUp)
    removeEvent(document.documentElement, 'mouseup', this.handleUp)
    removeEvent(document.documentElement, 'mousemove', this.move)
    removeEvent(document.documentElement, 'touchmove', this.move)
    removeEvent(window, 'resize', this.checkParentSize)
  },
  methods: {
    // 组件鼠标点击及组件位置数据重置
    resetBoundsAndMouseState () {
      this.mouseClickPosition = {
        mouseX: 0,
        mouseY: 0,
        x: 0,
        y: 0,
        w: 0,
        h: 0
      }
      this.bounds = {
        minLeft: null,
        maxLeft: null,
        minRight: null,
        maxRight: null,
        minTop: null,
        maxTop: null,
        minBottom: null,
        maxBottom: null
      }
    },
    // 获取父层节点宽高
    getParentSize () {
      if (this.parent) {
        const style = window.getComputedStyle(this.$el.parentNode, null)
        return [
          parseInt(style.getPropertyValue('width'), 10),
          parseInt(style.getPropertyValue('height'), 10),
        ]
      }
      return [null, null]
    },
    // 检测窗口尺寸
    checkParentSize () {},
    // 设置激活状态
    setActive () {
      this.enabled = true
      this.$emit('activated')
      this.$emit('update:active', true)
    },
    // 取消激活
    cancelActive () {
      this.enabled = false
      this.$emit('deactivated')
      this.$emit('update:active', false)
    },
    // 事件绑定方法
    // 停用
    deselect (e) {
      // console.log('点击外侧, 停用组件, 使组件不激活: ', e)
      if (e) {
        // 主动行为
        const target = e.target || e.srcElement
        // ? 这里不明白为什么用className 而不是 classNameHandle
        const regex = new RegExp(this.classNameHandle + '-([trmbl]{2})', '') // 正则匹配边角选择器
        // console.log(regex, regex.test(target.className))
        if (!this.$el.contains(target) && !regex.test(target.className)) {
          // 如果目标不在组件内或者不是点击的目标4个边角, 取消激活状态
          if (this.enabled && !this.preventDeactivation) {
            this.cancelActive()
          }
        }
      } else {
        // 立马取消激活
        this.cancelActive()
      }
      this.resetBoundsAndMouseState()
      removeEvent(document.documentElement, eventsFor.move, this.handleMove) // ?确认这里的hadleMove是否是真的绑定放法
    },
    // 松开鼠标, 移除鼠标按下事件
    handleUp () {
      // console.log('handleUp')
      this.handle = null
      this.resetBoundsAndMouseState()
      this.rawTop = this.top
      this.rawBottom = this.bottom
      this.rawLeft = this.left
      this.rawRight = this.right
      // 初始化辅助线数据
      const temArr = new Array(3).fill({
        display: false,
        position: '',
        origin: '',
        lineLength: ''
      })
      const refLine = {vLine: [], hLine: []}
      for (const i in refLine) {
        refLine[i] = JSON.parse(JSON.stringify(temArr))
      }
      // 如果是正在调整大小, 则关闭调整大小的状态
      if (this.resizing) {
        this.resizing = false
        this.$emit('on-line-params', refLine)
        this.$emit('resizestop', this.left, this.top, this.width, this.height)
      }
      // 如果是正在拖动, 则关闭拖动的状态
      if (this.dragging) {
        this.dragging = false
        this.$emit('on-line-params', refLine)
        this.$emit('dragstop', this.left, this.top)
      }
      removeEvent(document.documentElement, eventsFor.move, this.handleMove)
      // removeEvent(document.documentElement, eventsFor.move, this.move)
    },
    // 鼠标移动事件, 即移动改变尺寸事件
    handleMove (e) {
      // console.log('handleMove: ', e)
      const handle = this.handle
      const mouseClickPosition = this.mouseClickPosition
      const pageX = e.touches ? e.touches[0].pageX : e.pageX
      const pageY = e.touches ? e.touches[0].pageY : e.pageY
      const tmpDeltaX = mouseClickPosition.mouseX - pageX
      const tmpDeltaY = mouseClickPosition.mouseY - pageY
      const [deltaX, deltaY] = this.snapToGrid(this.grid, tmpDeltaX, tmpDeltaY)
      if (handle.includes('b')) {
        this.rawBottom = mouseClickPosition.bottom + deltaY
      } else if (handle.includes('t')) {
        this.rawTop = mouseClickPosition.top - deltaY
      }
      if (handle.includes('r')) {
        this.rawRight = mouseClickPosition.right + deltaX
      } else if (handle.includes('l')) {
        this.rawLeft = mouseClickPosition.left - deltaX
      }
      this.$emit('resizing', this.left, this.top, this.width, this.height)
    },
    // 元素被拖动事件
    elementMove (e) {
      // console.log('elementMove: ', e)
      const axis = this.axis // 辅助线
      const mouseClickPosition = this.mouseClickPosition // 鼠标点击的位置
      const pageX = e.touches ? e.touches[0].pageX : e.pageX // 横向移动偏移
      const pageY = e.touches ? e.touches[0].pageY : e.pageY // 纵向移动偏移
      const tmpDeltaX = axis && axis !== 'y' ? (mouseClickPosition.mouseX - pageX) : 0 // 鼠标实际偏移量
      const tmpDeltaY = axis && axis !== 'x' ? (mouseClickPosition.mouseY - pageY) : 0
      const [deltaX, deltaY] = this.snapToGrid(this.grid, tmpDeltaX, tmpDeltaY) // 计算缩放后的横纵轴偏移量
      this.rawTop = mouseClickPosition.top - deltaY // 偏移之后的鼠标位置
      this.rawBottom = mouseClickPosition.bottom + deltaY
      this.rawLeft = mouseClickPosition.left - deltaX
      this.rawRight = mouseClickPosition.right + deltaX
      this.snapCheck() // 设置对齐辅助线
      this.$emit('dragging', this.left, this.top) // 拖动, 这里的left和top是监听器监听rawLeft和rawTop时修改的
    },
    // 鼠标移动
    move (e) {
      // console.log('move: ', e)
      if (this.resizing) { // 正在改变尺寸
        this.handleMove(e)
      } else if (this.dragging) { // 正在拖动组件
        this.elementMove(e)
      }
    },
    // 鼠标按下操作
    elementDown (e) {
      // console.log('mouseDown鼠标按下: ', e)
      const target = e.target || e.srcElement
      // 检测当前点击元素是否在当前组件dom下
      if (this.$el.contains(target)) { // contains 用来查看dom元素的包含关系
        // 按下操作的前置检测
        if (this.onDragStart && this.onDragStart(e) === false) {
          return
        }
        // 判断目标节点直到基准节点是否具有某个选择器, 其实没啥用
        // 这里匹配鼠标点击的节点是在
        //   - 不含有dragHandle
        //   - 含有dragCancel
        // 意为: 在dranHandle内 或者 不在 dragCancel内 可操作
        // 其实就是个跳出行为, 鼠标不在节点内或者是取消节点就停止
        if (
          (this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||
          (this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))
        ) {
          return
        }
        // 是否激活, 等价于 active, 鼠标点击组件内节点，如果当前组件未激活, 则激活当前组件
        // 默认不激活
        if (!this.enabled) {
          this.setActive()
        }
        // 是否可拖动
        if (this.draggable) {
          this.dragging = true
        }
        this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
        this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY
        this.mouseClickPosition.left = this.left
        this.mouseClickPosition.right = this.right
        this.mouseClickPosition.top = this.top
        this.mouseClickPosition.bottom = this.bottom
        if (this.parent) {
          this.bounds = this.calcDragLimits() // 设置元素的拖动边界
        }
        addEvent(document.documentElement, eventsFor.move, this.move) // 增加修改监听move事件方法为currentElement的事件方法
        addEvent(document.documentElement, eventsFor.stop, this.handleUp) // 同上
      }
    },
    // 兼容touch事件
    elementTouchDown (e) {
      // console.log('touch鼠标按下: ', e)
      eventsFor = events.touch // 支持touch事件而不是mouseDown, 切换touch方法
      this.elementDown(e)
    },
    // 事件方法
    // 旋转
    handleRotateDown () {
      addEvent(document.documentElement, 'mousemove', this.handleRotateMove)
      addEvent(document.documentElement, 'mouseup', () => {
        removeEvent(document.documentElement, 'mousemove', this.handleRotateMove)
      })
      this.handleRotateStart(event)
    },
    // 旋转开始
    handleRotateStart (e) {
      const {clientX, clientY} = e.touches ? e.touches[0] : e
      const t = this.$refs['wrapper'].getBoundingClientRect() // 获取包装器的边界
      // console.log(t)
      const cx = t.left + t.width / 2 // ?
      const cy = t.top + t.height / 2
      const startAngle = (180 / Math.PI) * Math.atan2(clientY - cy, clientX - cx)
      const rotation = this.rawRotate
      // 这里应该是当前点击旋转后的组件的基础位置和角度
      this.rotateOpt = {
        cx,
        cy,
        startAngle,
        rotation
      }
    },
    // 旋转动作
    handleRotateMove (e) {
      const {
        cx,
        cy,
        startAngle,
        rotation
      } = this.rotateOpt
      const {clientX, clientY} = e.touches ? e.touches[0] : e
      const x = clientX - cx
      const y = clientY - cy
      const angle = (180 / Math.PI) * Math.atan2(y, x)
      const currentAngle = angle - startAngle
      let r = rotation + currentAngle
      r = r % 360
      r = r < 0 ? r + 360 : r
      this.rawRotate = Math.floor(r)
      this.$emit('on-rotate', this.rotate)
      // 可以考虑切片优化
      // window.requestAnimationFrame(() => {
      //   this.$emit('on-rotate', this.rotate)
      // })
    },
    // 拖动改变大小
    handleTouchDown (handle, e) {
      eventsFor = events.touch
      this.handleDown(handle, e)
    },
    handleDown (handle, e) {
      if (this.onResizeStart && this.onResizeStart(handle, e) === false) {
        return
      }
      if (e.stopPropagation) e.stopPropagation()
      this.currentHandle = handle
      // 如果锁定纵横比, 且点击的角不包含m, 则附上m的handle
      if (this.lockAspectRatio && !handle.includes('m')) { // 锁定纵横比
        this.handle = 'm' + handle.substring(1) // ? 这里会忽略bm和tm
      } else {
        this.handle = handle
      }
      this.resizing = true // 设置正在拖动的状态
      // 设置鼠标的当前坐标
      this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
      this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY
      this.mouseClickPosition.left = this.left
      this.mouseClickPosition.right = this.right
      this.mouseClickPosition.top = this.top
      this.mouseClickPosition.bottom = this.bottom
      this.bounds = this.calcResizeLimits() // 设置调整大小的边界
      addEvent(document.documentElement, eventsFor.move, this.handleMove)
      addEvent(document.documentElement, eventsFor.stop, this.handleup)
    },
    // 设置拖动边界
    calcDragLimits () {
      return {
        minLeft: (this.parentWidth + this.left) % this.grid[0],
        maxLeft: Math.floor((this.parentWidth - this.width - this.left) / this.grid[0]) * this.grid[0] + this.left,
        minRight: (this.parentWidth + this.right) % this.grid[0],
        maxRight: Math.floor((this.parentWidth - this.width - this.right) / this.grid[0]) * this.grid[0] + this.right,
        minTop: (this.parentHeight + this.top) % this.grid[1],
        maxTop: Math.floor((this.parentHeight - this.height - this.top) / this.grid[1]) * this.grid[1] + this.top,
        minBottom: (this.parentHeight + this.bottom) % this.grid[1],
        maxBottom: Math.floor((this.parentHeight - this.height - this.bottom) / this.grid[1]) * this.grid[1] + this.bottom
      }
    },
    // 计算调整大小的边界
    calcResizeLimits () {
      let minW = this.minW
      let minH = this.minH
      let maxW = this.maxW
      let maxH = this.maxH
      const aspectFactor = this.aspectFactor // 宽高比系数
      const [gridX, gridY] = this.grid // 二维
      const width = this.width
      const height = this.height
      const left = this.left
      const top = this.top
      const right = this.right
      const bottom = this.bottom
      if (this.lockAspectRatio) {
        if (minW / minH > aspectFactor) {
          minH = minW / aspectFactor
        } else {
          minW = aspectFactor * minH
        }
        if (maxW && maxH) {
          maxW = Math.min(maxW, aspectFactor * maxH)
          maxH = Math.min(maxH, maxW / aspectFactor)
        } else if (maxW) {
          maxH = maxW / aspectFactor
        } else if (maxH) {
          maxW = aspectFactor * maxH
        }
      }
      maxW = maxW - (maxW % gridX)
      maxH = maxH - (maxH % gridY)
      const limits = {
        minLeft: null,
        maxLeft: null,
        minTop: null,
        maxTop: null,
        minRight: null,
        maxRight: null,
        minBottom: null,
        maxBottom: null
      }
      if (this.parent) {
        limits.minLeft = (this.parentWidth + left) % gridX
        limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
        limits.minTop = (this.parentHeight + top) % gridY
        limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
        limits.minRight = (this.parentWidth + right) % gridX
        limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
        limits.minBottom = (this.parentHeight + bottom) % gridY
        limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY
        if (maxW) {
          limits.minLeft = Math.max(limits.minLeft, this.parentWidth - right - maxW)
          limits.minRight = Math.max(limits.minRight, this.parentWidth - left - maxW)
        }
        if (maxH) {
          limits.minTop = Math.max(limits.minTop, this.parentHeight - bottom - maxH)
          limits.minBottom = Math.max(limits.minBottom, this.parentHeight - top - maxH)
        }
        if (this.lockAspectRatio) {
          limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor)
          limits.minTop = Math.max(limits.minTop, top - left / aspectFactor)
          limits.minRight = Math.max(limits.minRight, right - bottom * aspectFactor)
          limits.minBottom = Math.max(limits.minBottom, bottom - right / aspectFactor)
        }
      } else {
        limits.minLeft = null
        limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
        limits.minTop = null
        limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
        limits.minRight = null
        limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
        limits.minBottom = null
        limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY
        if (maxW) {
          limits.minLeft = -(right + maxW)
          limits.minRight = -(left + maxW)
        }
        if (maxH) {
          limits.minTop = -(bottom + maxH)
          limits.minBottom = -(top + maxH)
        }
        if (this.lockAspectRatio && (maxW && maxH)) {
          limits.minLeft = Math.min(limits.minLeft, -(right + maxW))
          limits.minTop = Math.min(limits.minTop, -(maxH + bottom))
          limits.minRight = Math.min(limits.minRight, -left - maxW)
          limits.minBottom = Math.min(limits.minBottom, -top - maxH)
        }
      }
      return limits
    },
    // 计算偏移量在缩放后的量
    snapToGrid (grid, pendingX, pendingY) {
      const x = Math.round((pendingX / this.scale) / grid[0]) * grid[0]
      const y = Math.round((pendingY / this.scale) / grid[1]) * grid[1]
      return [
        x,
        y
      ]
    },
    // 设置属性
    settingAttribute () {
      this.$el.setAttribute('data-is-snap', (this.snap ? 'true' : 'false'))
    },
    // 检测元素对齐
    async snapCheck () {
      let width = this.width
      let height = this.height
      if (this.snap) {
        let activeLeft = this.rawLeft
        let activeRight = this.rawLeft + width
        let activeTop = this.rawTop
        let activeBottom = this.rawTop + height
        // 初始化辅助线数据
        const tempArr = new Array(3).fill({
          display: false, position: '', origin: '', lineLength: ''
        })
        const refLine = {
          vLine: [],
          hLine: []
        }
        for (const i in refLine) {
          refLine[i] = JSON.parse(JSON.stringify(tempArr))
        }
        // 获取父节点下的所有子节点
        const nodes = this.$el.parentNode.childNodes
        const temp = {
          value: {
            x: [[], [], []],
            y: [[], [], []]
          },
          display: [],
          position: []
        }
        const {
          groupWidth,
          groupHeight,
          groupLeft,
          groupTop,
          bln
        } = await this.getActiveAll(nodes)
        if (!bln) {
          width = groupWidth
          height = groupHeight
          activeLeft = groupLeft
          activeRight = groupLeft + groupWidth
          activeTop = groupTop
          activeBottom = groupTop + groupHeight
        }
        for (const item of nodes) {
          if (
            item.className !== undefined && 
            !item.className.includes(this.classNameActive) && 
            item.getAttribute('data-is-snap') !== null && 
            item.getAttribute('data-is-snap') !== 'false'
          ) {
            const w = item.offsetWidth
            const h = item.offsetHeight
            const l = item.offsetLeft // 对齐目标的left
            const r = l + w // 对齐目标的right
            const t = item.offsetTop // 对齐目标的top
            const b = t + h // 对齐目标的bottom
            const hc = Math.abs((activeTop + height / 2) - (t + h / 2)) <= this.snapTolerance // 水平中线
            const vc = Math.abs((activeLeft + width / 2) - (l + w / 2)) <= this.snapTolerance // 水平中线

            const ts = Math.abs(t - activeBottom) <= this.snapTolerance // 从上到下
            const TS = Math.abs(b - activeBottom) <= this.snapTolerance // 从上到下
            const bs = Math.abs(t - activeTop) <= this.snapTolerance // 从下到上
            const BS = Math.abs(b - activeTop) <= this.snapTolerance // 从下到上

            const ls = Math.abs(l - activeRight) <= this.snapTolerance // 外左
            const LS = Math.abs(r - activeRight) <= this.snapTolerance // 外左
            const rs = Math.abs(l - activeLeft) <= this.snapTolerance // 外右
            const RS = Math.abs(r - activeLeft) <= this.snapTolerance // 外右

            temp['display'] = [ts, TS, bs, BS, hc, hc, ls, LS, rs, RS, vc, vc]
            temp['position'] = [t, b, t, b, t + h / 2, t + h / 2, l, r, l, r, l + w / 2, l + w / 2]

            if (ts) {
              if (bln) {
                this.rawTop = t - height
                this.rawBottom = this.parentHeight - this.rawTop - height
              }
              temp.value.y[0].push(l, r, activeLeft, activeRight)
            }
            if (bs) {
              if (bln) {
                this.rawTop = t
                this.rawBottom = this.parentHeight - this.rawTop - height
              }
              temp.value.y[0].push(l, r, activeLeft, activeRight)
            }
            if (TS) {
              if (bln) {
                this.rawTop = b - height
                this.rawBottom = this.parentHeight - this.rawTop - height
              }
              temp.value.y[1].push(l, r, activeLeft, activeRight)
            }
            if (BS) {
              if (bln) {
                this.rawTop = b
                this.rawBottom = this.parentHeight - this.rawTop - height
              }
              temp.value.y[1].push(l, r, activeLeft, activeRight)
            }
            if (ls) {
              if (bln) {
                this.rawLeft = l - width
                this.rawRight = this.parentWidth - this.rawLeft - width
              }
              temp.value.x[0].push(t, b, activeTop, activeBottom)
            }
            if (rs) {
              if (bln) {
                this.rawLeft = l
                this.rawRight = this.parentWidth - this.rawLeft - width
              }
              temp.value.x[0].push(t, b, activeTop, activeBottom)
            }
            if (LS) {
              if (bln) {
                this.rawLeft = r - width
                this.rawRight = this.parentWidth - this.rawLeft - width
              }
              temp.value.x[1].push(t, b, activeTop, activeBottom)
            }
            if (RS) {
              if (bln) {
                this.rawLeft = r
                this.rawRight = this.parentWidth - this.rawLeft - width
              }
              temp.value.x[1].push(t, b, activeTop, activeBottom)
            }
            if (hc) {
              if (bln) {
                this.rawTop = t + h / 2 - height / 2
                this.rawBottom = this.parentHeight - this.rawTop - height
              }
              temp.value.y[2].push(l, r, activeLeft, activeRight)
            }
            if (vc) {
              if (bln) {
                this.rawLeft = l + w / 2 - width / 2
                this.rawRight = this.parentWidth - this.rawLeft - width
              }
              temp.value.x[2].push(t, b, activeTop, activeBottom)
            }
            // 辅助线坐标与是否显示(display)对应的数组,易于循环遍历
            const arrTemp = [0, 1, 0, 1, 2, 2, 0, 1, 0, 1, 2, 2]
            for (let i = 0; i <= arrTemp.length; i++) {
              // 前6为Y辅助线, 后6为X辅助线
              const xory = i < 6 ? 'y' : 'x'
              const horv = i < 6 ? 'hLine' : 'xLine'
              if (temp.display[i]) {
                const {
                  origin, length
                } = this.calcLineValues(temp.value[xory][arrTemp[i]])
                refLine[horv][arrTemp[i]].display = temp.display[i]
                refLine[horv][arrTemp[i]].position = temp.position[i] + 'px'
                refLine[horv][arrTemp[i]].origin = origin
                refLine[horv][arrTemp[i]].lineLength = length
              }
            }
          }
        }
        this.$emit('on-line-params', refLine)
      }
    },
    // 计算辅助线的值
    calcLineValues (arr) {
      const length = Math.max(...arr) - Math.min(...arr) + 'px'
      const origin = Math.min(...arr) + 'px'
      return {
        length,
        origin
      }
    },
    async getActiveAll (nodes) {
      const activeAll = []
      const XArray = []
      const YArray = []
      let groupWidth = 0
      let groupHeight = 0
      let groupLeft = 0
      let groupTop = 0
      for (const item of nodes) {
        if (item.className !== undefined && item.className.includes(this.classNameActive)) {
          activeAll.push(item)
        }
      }
      const AllLength = activeAll.length
      if (AllLength > 0) {
        for (const i of activeAll) {
          const l = i.offsetLeft
          const r = l + i.offsetWidth
          const t = i.offsetTop
          const b = t + i.offsetHeight
          XArray.push(t, b)
          YArray.push(l, r)
        }
        groupWidth = Math.max(...YArray) - Math.min(...YArray)
        groupHeight = Math.max(...XArray) - Math.min(...XArray)
        groupLeft = Math.min(...YArray)
        groupTop = Math.min(...XArray)
      }
      const bln = AllLength === 1
      return {
        groupWidth,
        groupHeight,
        groupLeft,
        groupTop,
        bln
      }
    }
  }
}
</script>

<style lang="less" scoped>
.draggable-resizable {
  touch-action: none;
  position: absolute;
  box-sizing: border-box;
  border: 1px dashed black;
  border: 0;
  &:before {
    pointer-events: none;
    position: absolute;
    content: '';
    border: 1px solid #6ccfff;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    display: none;
    box-sizing: border-box;
  }
  &.active:before,
  &:hover:before {
    display: block;
    z-index: 30;
  }
  .handle {
    z-index: 30;
    box-sizing: border-box;
    position: absolute;
    width: 10px;
    height: 10px;
    background: #eee;
    border: 1px solid #333;
  }
  .handle-tl {
    top: -10px;
    left: -10px;
    cursor: nw-resize;
  }
  .handle-tm {
    top: -10px;
    left: 50%;
    margin-left: -5px;
    cursor: n-resize;
  }
  .handle-tr {
    top: -10px;
    right: -10px;
    cursor: ne-resize;
  }
  .handle-ml {
    top: 50%;
    margin-top: -5px;
    left: -10px;
    cursor: w-resize;
  }
  .handle-mr {
    top: 50%;
    margin-top: -5px;
    right: -10px;
    cursor: e-resize;
  }
  .handle-bl {
    bottom: -10px;
    left: -10px;
    cursor: sw-resize;
  }
  .handle-bm {
    bottom: -10px;
    left: 50%;
    margin-left: -5px;
    cursor: s-resize;
  }
  .handle-br {
    bottom: -10px;
    right: -10px;
    cursor: se-resize;
  }
  // 旋转handle
  .handle-rotate {
    bottom: 0;
    left: 50%;
    margin-left: -1px;
    margin-bottom: -40px;
    width: 19px;
    height: 19px;
    cursor: grab;
    border: 0;
    transform: translate(-50%, 0);
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 0 2px #bbb;
    i{ z-index: 2; position: relative; padding: 3px; background: #fff; border-radius: 50%;}
    &:after{ position: absolute; content: ''; width: 1px; height: 20px; left: 50%; top: -18px; border-left: 1px dashed #6ccfff;}
  }
  @media only screen and (max-width: 768px) {
    [class*="handle-"]:before {
      content: '';
      left: -10px;
      right: -10px;
      bottom: -10px;
      top: -10px;
      position: absolute;
    }
  }
}
</style>